/**
 * Axelor Business Solutions
 *
 * Copyright (C) 2017 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.axelor.apps.bankpayment.ebics.client;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;

import org.jdom.JDOMException;

import com.axelor.apps.bankpayment.db.EbicsTransferState;
import com.axelor.apps.bankpayment.ebics.interfaces.ContentFactory;
import com.axelor.apps.bankpayment.ebics.io.ByteArrayContentFactory;
import com.axelor.apps.bankpayment.ebics.io.Joiner;
import com.axelor.apps.bankpayment.ebics.service.EbicsUserService;
import com.axelor.apps.bankpayment.ebics.xml.DInitializationRequestElement;
import com.axelor.apps.bankpayment.ebics.xml.DInitializationResponseElement;
import com.axelor.apps.bankpayment.ebics.xml.DTransferRequestElement;
import com.axelor.apps.bankpayment.ebics.xml.DTransferResponseElement;
import com.axelor.apps.bankpayment.ebics.xml.DefaultEbicsRootElement;
import com.axelor.apps.bankpayment.ebics.xml.InitializationResponseElement;
import com.axelor.apps.bankpayment.ebics.xml.ReceiptRequestElement;
import com.axelor.apps.bankpayment.ebics.xml.ReceiptResponseElement;
import com.axelor.apps.bankpayment.ebics.xml.TransferResponseElement;
import com.axelor.apps.bankpayment.ebics.xml.UInitializationRequestElement;
import com.axelor.apps.bankpayment.ebics.xml.UTransferRequestElement;
import com.axelor.exception.AxelorException;


/**
 * Handling of file transfers.
 * Files can be transferred to and fetched from the bank.
 * Every transfer may be performed in a recoverable way.
 * For convenience and performance reasons there are also
 * methods that do the whole transfer in one method call.
 * To use the recoverable transfer mode, you may set a working
 * directory for temporarily created files.
 * 
 * <p> EBICS specification 2.4.2 - 6.2 Encryption at application level
 * 
 * <p>In the event of an upload transaction, a random symmetrical key is generated in the
 * customer system that is used exclusively within the framework of this transaction both for
 * encryption of the ES’s and for encryption of the order data. This key is encrypted
 * asymmetrically with the financial institution’s public encryption key and is transmitted by the
 * customer system to the bank system during the initialization phase of the transaction.
 * 
 * <p>Analogously, in the case of a download transaction a random symmetrical key is generated
 * in the bank system that is used for encryption of the order data that is to be downloaded and
 * for encryption of the bank-technical signature that has been provided by the financial
 * institution. This key is asymmetrically encrypted and is transmitted by the bank system to the
 * customer system during the initialization phase of the transaction. The asymmetrical
 * encryption takes place with the technical subscriber’s public encryption key if the
 * transaction’s EBICS messages are sent by a technical subscriber. Otherwise the
 * asymmetrical encryption takes place with the public encryption key of the non-technical
 * subscriber, i.e. the submitter of the order.
 * 
 * @author Hachani
 *
 */
public class FileTransfer {

  /**
   * Constructs a new FileTransfer session
   * @param session the user session
   */
  public FileTransfer(EbicsSession session) {
    this.session = session;
  }

  /**
   * Initiates a file transfer to the bank.
   * @param content The bytes you want to send.
   * @param orderType As which order type
   * @throws IOException
   * @throws EbicsException
   */
  public void sendFile(byte[] content, OrderType orderType, byte[] signature)
    throws IOException, AxelorException
  {
    HttpRequestSender			sender;
    UInitializationRequestElement	initializer;
    InitializationResponseElement	response;
    int					httpCode;
    EbicsTransferState		state;

    sender = new HttpRequestSender(session);
    
    initializer = new UInitializationRequestElement(session,
	                                            orderType,
	                                            content, signature);
   
    initializer.build();

	initializer.validate();
	ByteArrayOutputStream bout = new ByteArrayOutputStream();
    try {
		initializer.save(bout);
	} catch (JDOMException e) {
		// TODO Bloc catch généré automatiquement
		e.printStackTrace();
	}
	System.out.println("Requete ----------------------------------------------------------------------------");
	System.out.println(bout.toString());
	
    httpCode = sender.send(new ByteArrayContentFactory(initializer.prettyPrint()));
    
    EbicsUtils.checkHttpCode(httpCode);
    response = new InitializationResponseElement(sender.getResponseBody(),
	                                         orderType,
	                                         DefaultEbicsRootElement.generateName(orderType),
	                                         session.getUser());
    response.build();
    response.report(new EbicsRootElement[]{initializer, response});
    state = new EbicsTransferState();
    state.setNumSegments(initializer.getSegmentNumber());
    state.setTransactionId(response.getTransactionId());
    int segNumber = 1;
    while( segNumber <= state.getNumSegments() ) {
      state.setSegmentNumber(segNumber);
	  if (segNumber == state.getSegmentNumber()) {
	    	state.setLastSegment(true);
	  }
      sendFile(initializer.getContent(segNumber),
    		  					  segNumber,
	                              state.getLastSegment(),
	                              state.getTransactionId(),
	                              orderType);
      segNumber++;
    }
  }

  /**
   * Sends a segment to the ebics bank server.
   * @param factory the content factory that contain the segment data.
   * @param segmentNumber the segment number
   * @param lastSegment is it the last segment?
   * @param transactionId the transaction Id
   * @param orderType the order type
   * @throws IOException
   * @throws EbicsException
   */
  public void sendFile(ContentFactory factory,
                       int segmentNumber,
                       boolean lastSegment,
                       byte[] transactionId,
                       OrderType orderType)
    throws IOException, AxelorException
  {
    UTransferRequestElement		uploader;
    HttpRequestSender			sender;
    TransferResponseElement		response;
    int					httpCode;

    uploader = new UTransferRequestElement(session,
	                                   orderType,
	                                   segmentNumber,
	                                   lastSegment,
	                                   transactionId,
	                                   factory);
    sender = new HttpRequestSender(session);
    uploader.build();
    uploader.validate();
    httpCode = sender.send(new ByteArrayContentFactory(uploader.prettyPrint()));
    EbicsUtils.checkHttpCode(httpCode);
    response = new TransferResponseElement(sender.getResponseBody(),
	                                   DefaultEbicsRootElement.generateName(orderType),
	                                   session.getUser());
    response.build();
    response.report(new EbicsRootElement[]{uploader, response});
  }

  /**
   * Fetches a file of the given order type from the bank.
   * You may give an optional start and end date.
   * This type of transfer will run until everything is processed.
   * No transaction recovery is possible.
   * @param orderType type of file to fetch
   * @param start optional begin of fetch term
   * @param end optional end of fetch term
   * @param dest where to put the data
   * @throws IOException communication error
   * @throws EbicsException server generated error
   */
  public void fetchFile(OrderType orderType,
                        Date start,
                        Date end,
                        OutputStream dest)
    throws IOException, AxelorException
  {
    HttpRequestSender			sender;
    DInitializationRequestElement	initializer;
    DInitializationResponseElement	response;
    ReceiptRequestElement	receipt;
    ReceiptResponseElement	receiptResponse;
    int	httpCode;
    EbicsTransferState	state;
    Joiner	joiner;

    sender = new HttpRequestSender(session);
    initializer = new DInitializationRequestElement(session,
	                                            orderType,
	                                            start,
	                                            end);
    initializer.build();
    initializer.validate();
    httpCode = sender.send(new ByteArrayContentFactory(initializer.prettyPrint()));
    EbicsUtils.checkHttpCode(httpCode);
    response = new DInitializationResponseElement(sender.getResponseBody(),
	                                          orderType,
	                                          DefaultEbicsRootElement.generateName(orderType),
	                                          session.getUser());
    response.build();
    response.report(new EbicsRootElement[]{initializer, response});
    state = new EbicsTransferState();
    state.setSegmentNumber(response.getSegmentsNumber());
    state.setTransactionId(response.getTransactionId());
    state.setSegmentNumber(response.getSegmentNumber());
    joiner = new Joiner(session.getUser());
    joiner.append(response.getOrderData());
    while(state.getSegmentNumber() <= state.getNumSegments()) {
      if(state.getSegmentNumber() == state.getNumSegments()) {
	    	state.setLastSegment(true);
	  }
      fetchFile(orderType,
    		state.getSegmentNumber(),
	        state.getLastSegment(),
	        state.getTransactionId(),
	        joiner);
      state.setSegmentNumber(state.getSegmentNumber() + 1);
    }

    joiner.writeTo(dest, response.getTransactionKey());
    receipt = new ReceiptRequestElement(session,
	                                state.getTransactionId(),
	                                DefaultEbicsRootElement.generateName(orderType));
    receipt.build();
    receipt.validate();
    httpCode = sender.send(new ByteArrayContentFactory(receipt.prettyPrint()));
    EbicsUtils.checkHttpCode(httpCode);
    receiptResponse = new ReceiptResponseElement(sender.getResponseBody(),
	                                         DefaultEbicsRootElement.generateName(orderType),
	                                         session.getUser());
    receiptResponse.build();
    receiptResponse.report(new EbicsRootElement[]{receipt, receiptResponse});
  }

  /**
   * Fetches a given portion of a file.
   * @param orderType the order type
   * @param segmentNumber the segment number
   * @param lastSegment is it the last segment?
   * @param transactionId the transaction ID
   * @param joiner the portions joiner
   * @throws IOException communication error
   * @throws EbicsException server generated error
   */
  public void fetchFile(OrderType orderType,
                        int segmentNumber,
                        boolean lastSegment,
                        byte[] transactionId,
                        Joiner joiner)
    throws IOException, AxelorException
  {
    DTransferRequestElement		downloader;
    HttpRequestSender			sender;
    DTransferResponseElement		response;
    int					httpCode;

    sender = new HttpRequestSender(session);
    downloader = new DTransferRequestElement(session,
	                                     orderType,
	                                     segmentNumber,
	                                     lastSegment,
	                                     transactionId);
    downloader.build();
    downloader.validate();
    httpCode = sender.send(new ByteArrayContentFactory(downloader.prettyPrint()));
    EbicsUtils.checkHttpCode(httpCode);
    response = new DTransferResponseElement(sender.getResponseBody(),
	                                    orderType,
	                                    DefaultEbicsRootElement.generateName(orderType),
	                                    session.getUser());
    response.build();
    response.report(new EbicsRootElement[]{downloader, response});
    joiner.append(response.getOrderData());
  }

  // --------------------------------------------------------------------
  // DATA MEMBERS
  // --------------------------------------------------------------------

  private EbicsSession			session;
}
